//去重
function noRepeat(arr){
  return Array.from( new Set(arr) )
}
// 专门存放我书写的各种功能函数
// 范围内的随机整数
function randomNum(min, max) {
  var a = Math.min(min, max)
  var b = Math.max(min, max)
  var res = Math.floor(Math.random() * (b - a + 1)) + a
  return res
}

// 随机颜色字符串
function randomColor() {
  var color = '#'
  for (var i = 0; i < 6; i++) color += randomNum(0, 15).toString(16)
  return color
}

// 获取时间差
function diffTime(time1, time2) {
  var sub = Math.ceil(Math.abs(time1.getTime() - time2.getTime()) / 1000)
  return {
    day: parseInt(sub / (60 * 60 * 24)),
    hours: parseInt(sub % (60 * 60 * 24) / (60 * 60)),
    minutes: parseInt(sub % (60 * 60) / 60),
    seconds: sub % 60
  }
}
// 多属性运动函数  16天
function move(ele, target, fn) {
  let count = 0

  for (let key in target) {
    count++
    if (key === 'opacity') target[key] *= 100
    const timerId = setInterval(() => {
      // 1. 每次获取 元素 当前位置
      let current
      if (key === 'opacity') {
        current = window.getComputedStyle(ele)[key] * 100
      } else {
        current = parseInt(window.getComputedStyle(ele)[key])
      }

      // 2. 计算本次的运动距离
      let distance = (target[key] - current) / 10
      distance = distance > 0 ?  Math.ceil(distance) : Math.floor(distance)

      // 3. 判断是否进行赋值
      if (current === target[key]) {
        clearInterval(timerId)
        count--
        if (count === 0) fn()
      } else {
        if (key === 'opacity') {
          ele.style[key] = (current + distance) / 100
        } else {
          ele.style[key] = current + distance + 'px'
        }
      }
    }, 20)
  }
}



//选项卡
function Tabs(select, type = 'click') {
  this.ele = document.querySelector(select)
  this.btns = this.ele.querySelectorAll('ul > li')
  this.boxs = this.ele.querySelectorAll('ol > li')
  this.type = type
  this.change()
}

Tabs.prototype.change = function () {
  for (let i = 0; i < this.btns.length; i++) {
    this.btns[i].addEventListener(this.type, () => {
      for (let j = 0; j < this.btns.length; j++) {
        this.btns[j].classList.remove('active')
        this.boxs[j].classList.remove('active')
      }
      this.btns[i].classList.add('active')
      this.boxs[i].classList.add('active')
    })
  }
}

//放大镜
function Enlarge(select) {
  // 范围元素
  this.ele = document.querySelector(select)

  // .show 的 div 盒子, 结构: div > img
  this.show = this.ele.querySelector('.show')

  // .mask 的 div 盒子(遮罩层), 结构: .show > .mask
  this.mask = this.ele.querySelector('.mask')

  // .list 的 div 盒子(列表), 结构: .list > p*2 > img
  this.list = this.ele.querySelector('.list')

  // .enlarge 的 div 盒子, 背景图片
  this.enlarge = this.ele.querySelector('.enlarge')

  // show 盒子的尺寸 如何获取
  // 元素.offsetWidth 和 offsetHeight, 获取到的是元素 内容 + padding + border 的尺寸
  this.show_width = this.show.offsetWidth
  this.show_height = this.show.offsetHeight

  // enlarge 盒子的尺寸 如何获取
  // 注意: enlarge 盒子默认是隐藏的
  // 获取元素非行内样式
  // 语法: widnow.getComputedStyle(要获取样式的元素).样式名
  this.enlarge_width = parseInt(window.getComputedStyle(this.enlarge).width)
  this.enlarge_height = parseInt(window.getComputedStyle(this.enlarge).height)

  // 背景图的尺寸 如何获取
  // 非行内样式
  this.bg_width = parseInt(window.getComputedStyle(this.enlarge).backgroundSize.split(' ')[0])
  this.bg_height = parseInt(window.getComputedStyle(this.enlarge).backgroundSize.split(' ')[1])

  // 调用启动器
  this.init()
}

// 书写方法
// 0. 启动器
Enlarge.prototype.init = function () {
  // 去调用所有的方法
  this.setScale()
  this.overOut()
  this.event()
  this.move()
}

// 1. 调整比例
/*
  1. 计算出 mask 盒子的宽度
    mask 盒子的尺寸      enlarge 盒子的尺寸
    --------------  =  -----------------
    show 盒子的尺寸      enlarge 盒子的背景
    mask 盒子的尺寸 = enlarge 盒子的尺寸 * show 盒子的尺寸 / enlarge 盒子的背景
  2. 给 this.mask 盒子设置 width 和 height
    + 设置元素的行内样式
*/
Enlarge.prototype.setScale = function () {

  // 1. 计算出 mask 的 尺寸
  this.mask_width = this.enlarge_width * this.show_width / this.bg_width
  this.mask_height = this.enlarge_height * this.show_height / this.bg_height

  // 2. 给 this.mask 盒子赋值
  this.mask.style.width = this.mask_width + 'px'
  this.mask.style.height = this.mask_height + 'px'

}

// 2. 移入移出
/*
  1. 绑定事件
    => 给谁绑定: this.show 盒子
    => 移入的时候, mask 和 enlarge 显示
    => 移出的时候, mask 和 enlarge 隐藏
*/
Enlarge.prototype.overOut = function () {
  // 绑定移入事件
  this.show.addEventListener('mouseenter', () => {
    this.mask.style.display = 'block'
    this.enlarge.style.display = 'block'
  })

  // 绑定移出事件
  this.show.addEventListener('mouseleave', () => {
    this.mask.style.display = 'none'
    this.enlarge.style.display = 'none'
  })
}

// 3. 点击切换
/*
  1. 绑定点击事件
    => 给谁绑定 : this.list
      -> 因为将来可能会有很多可以切换的, 并且这些内容基本上都是渲染出来
      -> 使用事件委托的形式
  2. 切换显示的图片
    => 如何切换
      -> 在结构标签上 以自定义属性的方式, 把需要切换的 路径标注一下
      -> 在点击的时候, 拿到标签身上的自定义属性
  3. 给指定的内容切换图片显示
    => showUrl 赋值给谁
      -> 赋值给 this.show 的第一个子元素的 src 属性
    => enlargeUrl 赋值给谁
      -> 赋值给 this.enlarge 的 backgroundImage 属性
  4. 切换 active 类名
    => 给谁去掉 active 类名
      -> 所有的 p 标签, 就是 this.list 内的所有子元素
    => 给谁加上 active 类名
      -> target 是你点击的这个 img 标签
      -> 给 img 标签的 父元素 p 标签加上 active
*/
Enlarge.prototype.event = function () {
  // 1. 事件委托给 this.list 绑定点击事件
  // 什么是事件对象 : 当事件触发的时候, 一个描述本次事件的一些相关信息的 对象数据类型
  // 什么是事件目标 : 准确触发事件的元素
  // 什么是事件源 : 绑定在谁身上的事件
  this.list.addEventListener('click', e => {
    // 处理事件对象兼容
    e = e || window.event
    // 处理事件目标兼容
    const target = e.target || e.srcElement

    // 条件判断事件目标
    if (target.tagName === 'IMG') {
      // 2. 拿到自定义属性
      const showUrl = target.dataset.show
      const enlargeUrl = target.dataset.enlarge

      // 3. 开始赋值
      this.show.firstElementChild.src = showUrl
      this.enlarge.style.backgroundImage = `url(${ enlargeUrl })`

      // 4. 切换 active 类名
      // 4-1. 给 this.list 内所有的子元素去掉类名
      for (let i = 0; i < this.list.children.length; i++) {
        this.list.children[i].classList.remove('active')
      }
      // 4-2. 给 p 标签加上 active 类名
      target.parentNode.classList.add('active')
    }
  })
}

// 4. 移动联动
/*
  1. 给元素绑定事件
    => 给谁 绑定什么 事件
    => 事件类型: mousemove
    => 事件源: this.show
    => 因为只要光标在 show 盒子范围内移动的时候, mask 盒子都要移动
  2. 获取坐标点
    => 方案1:
      -> 获取相对于页面左上角的坐标点
      -> 注意: 不要用相对于窗口的, 要用相对于页面的
      -> 减去 ele 盒子相对于页面左边和上边的偏移量
      -> 在减去 ele 盒子的 1px 的边框
    => 方案2:
      -> 获取相对于元素左上角的坐标位置
      -> 因为 mask 刚好是相对于 show 盒子进行的定位
      -> 如果我能拿到相对于 show 盒子左上角的位置
      -> 相对于 事件目标 的左上角
      -> 当你光标移入到 mask 盒子里面以后, 坐标位置就变了
      -> 只要 mask 盒子不作为事件目标
      -> 可以给 mask 盒子添加一个样式 pointer-events: none;
  3. 进行边界值判断
    => 左: x <= 0 让 x = 0
    => 上: y <= 0 让 y = 0
    => 右: x >= show_width - mask_width
    => 下: y >= show_height - mask_height

  4. 把 x 和 y 赋值给 mask 盒子
    => 分别赋值给 left 和 top

  5. 让背景图联动
    => 我们只要计算出尺寸, 让他以负数的形式赋值
    => 比例: 背景图的尺寸 / 图片盒子的尺寸(show盒子的尺寸) * mask 盒子移动的距离
*/
Enlarge.prototype.move = function () {

  // 1. 给 this.show 绑定鼠标移动事件
  this.show.addEventListener('mousemove', e => {
    // 处理事件对象兼容
    e = e || window.event

    // 2. 获取光标的坐标点
    // 方案1:
    // let x = e.pageX - this.ele.offsetLeft - this.ele.clientLeft - this.mask_width / 2
    // let y = e.pageY - this.ele.offsetTop - this.ele.clientTop - this.mask_width / 2
    // 方案2:
    let x = e.offsetX - this.mask_width / 2
    let y = e.offsetY - this.mask_height / 2

    // 3. 边界值判断
    if (x <= 0) x = 0
    if (y <= 0) y = 0
    if (x >= this.show_width - this.mask_width) x = this.show_width - this.mask_width
    if (y >= this.show_height - this.mask_height) y = this.show_height - this.mask_height

    // 4. 给 mask 的 left 和 top 进行赋值
    this.mask.style.left = x + 'px'
    this.mask.style.top = y + 'px'

    // 5. 让背景图联动
    // 计算出数字
    // 比例: 背景图的尺寸 / 图片盒子的尺寸(show盒子的尺寸) * mask 盒子移动的距离
    const bg_x = this.bg_width / this.show_width * x
    const bg_y = this.bg_height / this.show_height * y

    // 给 enlarge 盒子的 backgroundPosition 设置值
    this.enlarge.style.backgroundPosition = `-${ bg_x }px -${ bg_y }px`
  })
}

//轮播图
            class Banner {
            constructor (select) {
              // 范围元素
              this.ele = document.querySelector(select)
              // 承载图片的盒子
              this.imgBox = this.ele.querySelector('.imgBox')
              // 承载焦点的盒子
              this.pointBox = this.ele.querySelector('.pointBox')


              // 准备一个变量, 表示当前是第几张
              this.index = 0
              // 准备一个变量, 表示定时器
              this.timerId = 0



              // 调用启动器
              this.init()
            }

            // 启动器
            init () {
              // 调用设置焦点的方法
              this.setPoint()
              // 调用自动轮播的方法
              this.autoPlay()
              // 调用移入移出的方法
              this.overOut()
              // 调用绑定事件的方法
              this.bindEvent()
            }

            // 设置焦点
            setPoint () {
              // 1. 获取到需要生成多少个焦点
              const pointNum = this.imgBox.children.length

              // 2. 循环遍历生成焦点盒子
              const frg = document.createDocumentFragment()

              for (let i = 0; i < pointNum; i++) {
                // 创建 li
                const li = document.createElement('li')

                // 给每一个 li 添加一个类名, 为了和 图片的 li 做一个区分
                li.classList.add('point')

                // 默认给第一个 li 添加 active 类名
                if (i === 0) li.classList.add('active')

                // 记录索引
                li.dataset.i = i

                // 把 li 放在筐里面
                frg.appendChild(li)
              }

              // 3. 把 frg 放在 this.pointBox 内部
              this.pointBox.appendChild(frg)

              // 4. 选做: 调整一下焦点盒子的宽度
              this.pointBox.style.width = pointNum * (20 + 10) + 'px'
            }

            // 切换一张
            changeOne (type) {
              // 1. 不管切换到哪一张, 都需要当前这一张消失
              // 此时 this.index 表示的是当前这一张
              // 需要让 imgBox 内部索引为 [this.index] 的那一张取消 active 类名
              this.imgBox.children[this.index].classList.remove('active')
              // 操作焦点
              this.pointBox.children[this.index].classList.remove('active')

              // 2. 通过 type 来判断, 决定 this.index 的值
              switch (type) {
                case true:
                  // 下一张
                  this.index++
                  break
                case false:
                  // 上一张
                  this.index--
                  break
                default:
                  // 某一张
                  this.index = type
              }

              // 3. 调整一下 this.index 的边界值
              if (this.index < 0) this.index = this.imgBox.children.length - 1
              if (this.index >= this.imgBox.children.length) this.index = 0

              // 4. 让该显示的显示出来
              // 索引为 this.index 的显示出来
              this.imgBox.children[this.index].classList.add('active')
              this.pointBox.children[this.index].classList.add('active')
            }

            // 自动轮播
            autoPlay () {
              // 1. 开启定时器
              this.timerId = setInterval(() => {
                // 2. 切换下一张
                this.changeOne(true)
              }, 2000)
            }

            // 移入移出
            overOut () {
              // 1. 移入事件
              this.ele.addEventListener('mouseover', () => clearInterval(this.timerId))

              // 2. 移出事件
              this.ele.addEventListener('mouseout', () => this.autoPlay())
            }

            // 点击事件
            bindEvent () {
              // 1. 给 this.ele 绑定点击事件
              this.ele.addEventListener('click', e => {
                // 处理事件对象兼容
                e = e || window.event
                // 处理事件目标兼容
                const target = e.target || e.srcElement

                // 2. 判断点击的是左按钮
                if (target.className === 'prev') {
                  // 切换上一张
                  this.changeOne(false)
                }

                // 3. 判断点击的是右按钮
                if (target.className === 'next') {
                  // 切换下一张
                  this.changeOne(true)
                }

                // 4. 判断点击的是焦点按钮
                if (target.className === 'point') {
                  // 需要拿到你点击的这个 li 的索引
                  // 提前把索引以自定义属性的形式记录下来
                  const i = target.dataset.i - 0
                  // 切换某一张
                  this.changeOne(i)
                }
              })
            }
            }
            
// 封装 ajax
function ajax(options = {}) {
  const { url, method, async, data, token, dataType, success, error } = options

  // 1. 参数验证
  if (!url) {
    throw new Error('url 为必填选项 ^_^ !')
  }
  if (!(method === undefined || /^(get|post)$/i.test(method))) {
    throw new Error('目前只接受 GET 和 POST 请求方式, 请期待更新 O(∩_∩)O哈哈~ ')
  }
  if (!(async === undefined || typeof async === 'boolean')) {
    throw new Error('async 需要传递一个 布尔值 (#^.^#)')
  }
  if (!(data === undefined || typeof data === 'string')) {
    throw new Error('data 需要传递一个 字符串 类型')
  }
  if (!(token === undefined || typeof token === 'string')) {
    throw new Error('token 需要传递一个 字符串 类型')
  }
  if (!(dataType === undefined || /^(string|json)$/i.test(dataType))) {
    throw new Error('dataType 请传递一个 "string" 或者 "json"')
  }
  if (!(success === undefined || typeof success === 'function')) {
    throw new Error('success 需要传递一个 函数数据 类型')
  }
  if (!(error === undefined || typeof error === 'function')) {
    throw new Error('error 需要传递一个 函数数据 类型')
  }

  // 2. 准备一套默认值
  const _default = {
    url,
    method: method || 'GET',
    async: async ?? true,
    data: data || '',
    token: token || '',
    dataType: dataType || 'string',
    success: success || function () {},
    error: error || function () {}
  }
  if (/^get$/i.test(_default.method) && _default.data) {
    _default.url = _default.url + '?' + _default.data
  }

  // 3. 发送请求
  const xhr = new XMLHttpRequest()
  xhr.open(_default.method, _default.url, _default.async)
  xhr.onload = function () {
    if (_default.dataType === 'string') {
      _default.success(xhr.responseText)
      return
    }
    try {
      let result = JSON.parse(xhr.responseText)
      _default.success(result)
    } catch (err) {
      _default.error(err, xhr.responseText)
    }
  }
  if (_default.token) xhr.setRequestHeader('authorization', _default.token)
  if (/^post$/i.test(_default.method)) {
    xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded')
    xhr.send(_default.data)
  } else {
    xhr.send()
  }
}


// Promise 的形式封装 ajax
function pAjax(options = {}) {
  const p = new Promise(function (resolve, reject) {
    // 利用原先封装好的 ajax 函数来完成请求
    ajax({
      url: options.url,
      data: options.data,
      async: options.async,
      token: options.token,
      method: options.method,
      dataType: options.dataType,
      success (res) {
        resolve(res)
      },
      error (err, message) {
        reject(message)
      }
    })
  })

  return p
}



